using System;
using System.Linq;
using System.Text.RegularExpressions;
using SQLRunnerLib.Services;
using SQLRunnerLib.Util;
using SQLServices.Log;
using SQLServices.Runners;
using SQLServices.Runners.Events;

namespace SQLRunnerLib.Util
{
	/// <summary>
	/// Manages the execution of a SQL Runner project from the Command line
	/// </summary>
	public class LineController
	{
	    private long _scriptsExecuted = 0;
		private long _scriptsWithErrors = 0;
		private LineParser _args;
		private IRunner _runner;

        // It is worth noting that this method may update the values of the passed args
		private bool ValidateArgs(LineParser args, bool isTestMode) 
		{
			// we need a project file
			if (args.Project == null) 
			{
				ServiceContainer.Logger.LogMessage("Missing a valid project file", MsgLevel.Error);
                return ValidationFailure(isTestMode);
			}
			// The project file should contain at least one folder
			if (args.Project.Folders.Count == 0) 
			{
				ServiceContainer.Logger.LogMessage("Project file should contain at least one folder", MsgLevel.Error);
                return ValidationFailure(isTestMode);
			}
		    var hasEnvironments = args.Project.Environments.Count > 0;
            if (!hasEnvironments)
            {
                ServiceContainer.Logger.LogMessage("This project file does not contain an environment section, was this project created in an old version?");
            }

			// The server name should be populated
		    args.ServerName = GetServerName(args, hasEnvironments);
            if (args.ServerName.Equals(string.Empty)) 
			{
                ServiceContainer.Logger.LogMessage("Server name is missing", MsgLevel.Error);
                return ValidationFailure(isTestMode);
			}
			// The database name should be populated
		    args.DatabaseName = GetDatabaseName(args, hasEnvironments);
            if (args.DatabaseName.Equals(string.Empty)) 
			{
				// try to get database name from the project file
                ServiceContainer.Logger.LogMessage("Database name is missing", MsgLevel.Error);
                return ValidationFailure(isTestMode);
			}            
            // Check user connection details
            CheckConnDetails(args);
            if (args.UserName.Equals(string.Empty))
                args.TrustedConnection = true;

			if (args.ShowHelp) return ValidationFailure(isTestMode);
			
			return true;
		}

	    private bool ValidationFailure(bool isTestMode)
	    {
	        if (!isTestMode) LineUsage.Print();
	        return false;
	    }

	    // To use only for testing
        public bool TestValidateArgs(LineParser args)
        {
            return ValidateArgs(args, true);
        }

	    private void CheckConnDetails(LineParser args)
	    {
            // if trusted connection was declared in the command line we dont need to do anything else
	        if (args.TrustedConnection) return;
            // if user name was declared in the command line then we use it
            if (!args.UserName.Equals(string.Empty)) return;
            // check if we have an username in the environment
            var environment = args.Project.SelectedEnvironment ?? args.Project.Environments.FirstOrDefault();
            if (environment == null) return;
	        args.TrustedConnection = environment.Trusted;
            if (args.TrustedConnection) return;
            // if we got this far then we should have a user name in the environment
	        args.UserName = environment.Username;
	        args.Pwd = environment.Password;
	    }

	    private string GetServerName(LineParser args, bool hasEnvironments)
	    {
	        var serverName = args.ServerName;
	        if (serverName.Equals(string.Empty) && hasEnvironments)
	        {
	            var env = args.Project.SelectedEnvironment ?? args.Project.Environments.FirstOrDefault();
	            serverName = env.ServerName;                
	        }
	        return serverName;
	    }

        private string GetDatabaseName(LineParser args, bool hasEnvironments)
        {
            var databaseName = args.DatabaseName;
            if (databaseName.Equals(string.Empty) && hasEnvironments)
            {
                var env = args.Project.SelectedEnvironment ?? args.Project.Environments.FirstOrDefault();
                databaseName = env.DatabaseName;
            }
            return databaseName;
        }

	    private void Connect(LineParser args) 
		{
			ServiceContainer.Logger.LogMessage("Connecting to the SQL Server", MsgLevel.Info);
			ServiceContainer.SQLConnection.OpenConnection(args.ServerName, args.UserName, args.Pwd, args.TrustedConnection);
			ServiceContainer.Logger.LogMessage(string.Format("Connected to SQL Server: {0}", args.ServerName), MsgLevel.Info);
			ServiceContainer.SQLConnection.GetDatabase(args.DatabaseName);
			ServiceContainer.Logger.LogMessage(string.Format("Connected to Database: {0}", args.DatabaseName), MsgLevel.Info);
		}

		public int Start(LineParser args)
		{
			if (!ValidateArgs(args, false)) return -1;
			try {
				_args = args;
				Connect(args);
				_runner = ServiceContainer.CreateRunner(args.Project);
                _runner.ConnectionMgrType = _args.ConnMgrType;
				HookEvents(_runner);
				_runner.RunScripts();
				UnHookEvents(_runner);
				ServiceContainer.SQLConnection.Disconnect();
				ServiceContainer.Logger.LogMessage("Disconnected from the SQL Server", MsgLevel.Info);
				return (int) _scriptsWithErrors;
			}
			catch (Exception ex) {
				Regex expression = new Regex(@"(\[(\w*\s*)*\])+");
				string msg = expression.Replace(ex.Message, "");
				ServiceContainer.Logger.LogMessage(
					String.Format("The application stopped as a result of:\n{0}", msg),
					MsgLevel.Error);
				return -1;
			}
		}

		private void HookEvents(IRunner runner)
		{
			runner.ProgressMsgCreated += OnProgressMsgCreated;
			runner.ScriptExecError += OnScriptExecError;
			runner.ScriptExecuted += OnScriptExecuted;
			runner.ExecutionFinished += OnExecutionFinished;
		}

		private void UnHookEvents(IRunner runner)
		{
			runner.ProgressMsgCreated -= OnProgressMsgCreated;
			runner.ScriptExecError -= OnScriptExecError;
			runner.ScriptExecuted -= OnScriptExecuted;
			runner.ExecutionFinished -= OnExecutionFinished;
		}

		private void OnProgressMsgCreated(object sender, ProgressMsgCreatedEventArgs e)
		{
			ServiceContainer.Logger.LogMessage(e.Message, MsgLevel.Info);
		}

		private void OnScriptExecError(object sender, ScriptExecutionErrorEventArgs e)
		{
			ServiceContainer.Logger.LogMessage(
				String.Format("Error executing [{0}]: {1}", e.FileName, e.ErrorMsg),
				MsgLevel.Error);
			_scriptsExecuted++;
			_scriptsWithErrors++;
			if ( ( _args.ErrorLevel & ErrorIndicator.StopExecution ) == ErrorIndicator.StopExecution ) {
				_runner.CancelExecution();
			}
		}

		private void OnScriptExecuted(object sender, ScriptExecutedEventArgs e)
		{
			ServiceContainer.Logger.LogMessage(
				String.Format("Runner - script {0} was executed", e.FileName));
			_scriptsExecuted++;
		}

		private void OnExecutionFinished(object sender, ExecutionFinishedEventArgs e)
		{
			if ( _scriptsExecuted > 0 )
				ServiceContainer.Logger.LogMessage(
					String.Format("{0} scrips was/were executed", _scriptsExecuted),
					MsgLevel.Warn);
			if ( _scriptsWithErrors > 0 )
				ServiceContainer.Logger.LogMessage(
					String.Format("{0} scrips was/were executed with errors", _scriptsWithErrors),
					MsgLevel.Warn);
			ServiceContainer.Logger.LogMessage("Application finishing", MsgLevel.Info);
		}
	}

}
